using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System;
using System.Threading.Tasks;

namespace Yukisky.CommonUtils
{
    public class UnityMainThreadDispatcher : MonoBehaviour
    {
        private static readonly Queue<Action> _executionQueue = new Queue<Action>();

		public void Update() {
			lock(_executionQueue) {
				while (_executionQueue.Count > 0) {
					_executionQueue.Dequeue().Invoke();
				}
			}
		}

		/// <summary>
		/// Locks the queue and adds the IEnumerator to the queue
		/// </summary>
		/// <param name="action">IEnumerator function that will be executed from the main thread.</param>
		public void Enqueue(IEnumerator action) {
			lock (_executionQueue) {
				_executionQueue.Enqueue (() => {
					StartCoroutine (action);
				});
			}
		}

	        /// <summary>
	        /// Locks the queue and adds the Action to the queue
		/// </summary>
		/// <param name="action">function that will be executed from the main thread.</param>
		public void Enqueue(Action action)
		{
			Enqueue(ActionWrapper(action));
		}

        public void Enqueue(Action action, float time)
        {
	        Enqueue(ActionWaitWrapper(action,time));
        }
		
		/// <summary>
		/// Locks the queue and adds the Action to the queue, returning a Task which is completed when the action completes
		/// </summary>
		/// <param name="action">function that will be executed from the main thread.</param>
		/// <returns>A Task that can be awaited until the action completes</returns>
		public Task EnqueueAsync(Action action)
		{
			var tcs = new TaskCompletionSource<bool>();

			Action WrappedAction = () =>
			{
				try
				{
					action();
					tcs.TrySetResult(true);
				}
				catch (Exception ex)
				{
					tcs.TrySetException(ex);
				}
			};

			Enqueue(ActionWrapper(WrappedAction));
			return tcs.Task;
		}

		
		IEnumerator ActionWrapper(Action a)
		{
			a();
			yield return null;
			
		}
		
		IEnumerator ActionWaitWrapper(Action a,float time)
		{
			yield return new WaitForSeconds(time);
			a();
			yield return null;
			
		}


		private static UnityMainThreadDispatcher _instance = null;

		public static bool Exists() {
			return _instance != null;
		}

		public static UnityMainThreadDispatcher Instance() {
			if (!Exists ()) {
				GameObject go = new GameObject("UnityMainThreadDispatcher");
				go.AddComponent<UnityMainThreadDispatcher>();
				DontDestroyOnLoad(go);
			}
			return _instance;
		}

		private void Awake()
		{
			if (_instance != null)
			{
				Destroy(gameObject);
				return;
			}

			_instance = this;
		}
    }
}